package task2_2.com.at.count;

import task2_2.com.at.count.polsk.PolNotationGenerator;
import task2_2.com.at.count.polsk.Grammar;
import task2_2.com.at.count.polsk.CounterException;
import task2_2.com.at.count.polsk.Lexeme;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

public class Counter {

    private String expression;

    private final String SIN = "sin";
    private final String COS = "cos";
    private final String POW = "pow";
    private final String TAN = "tan";
    private final String ABS = "abs";

    private Map functions;
    private Map params;
    private Grammar grammar;

    private List polskLexemes;
    private List _polskLexemes;
    private List lexemesInFunc;

    public Counter() throws CounterException {
        this.expression = null;
        this.params = new HashMap();

        this.functions = new HashMap();
        this.functions.put(SIN, new Function(SIN, 1));
        this.functions.put(COS, new Function(COS, 1));
        this.functions.put(TAN, new Function(TAN, 1));
        this.functions.put(ABS, new Function(ABS, 1));
        this.functions.put(POW, new Function(POW, 2));

        this.polskLexemes = null;

        grammar = new Grammar(null);
    }

    protected Counter(Map functions, Grammar grammar) {
        this.expression = null;
        this.params = new HashMap();
        this.functions = functions;
        this.grammar = grammar;
        this.polskLexemes = null;
    }

    public void setGrammar(String path) throws CounterException {
        grammar.setGrammar(path);
    }

    public void setExpression(String expression) {
        this.expression = expression;
    }

    public BigDecimal count() throws CounterException {
        if (expression == null) {
            throw new CounterException();
        }

        if (polskLexemes == null) {
            generateInvertedPolNotayion();
        }


        _polskLexemes.clear();
        for (int k=0; k<polskLexemes.size(); k++) {
            _polskLexemes.add(polskLexemes.get(k));
        }

        lexemesInFunc.clear();

        for (int k=0; k<_polskLexemes.size(); k++) {
            Lexeme lex = (Lexeme) _polskLexemes.get(k);
            if (lex.getType() == Lexeme.TYPE_NUMBER) {
//                lex.setDouleValue();
                lex.setBigDecimalValue();
            } else if (lex.getType() == Lexeme.TYPE_TERM) {
                Parameter param = (Parameter) params.get(lex.getValue());
                if (param == null) {
                    throw new CounterException();
                }
//                lex.setDouleValue(param.getValue());
                lex.setBigDecimalValue(new BigDecimal(param.getValue()));
            }
        }

        int i = 0;
        while (_polskLexemes.size() > 1) {
            Lexeme lex = (Lexeme) _polskLexemes.get(i);
            if (lex.getType() == Lexeme.TYPE_ZNAK && lex.getNumberCountUsed() > 1) {
                Lexeme one = (Lexeme) _polskLexemes.get(i - 2);
                Lexeme two = (Lexeme) _polskLexemes.get(i - 1);
                BigDecimal d_one = one.getBigDecimalValue();
                BigDecimal d_two = two.getBigDecimalValue();
                if (lex.getValue().equals("+")) {
//                    one.setDouleValue(d_one + d_two);
                    one.setBigDecimalValue(d_one.add(d_two));
                } else if (lex.getValue().equals("-")) {
//                    one.setDouleValue(d_one - d_two);
                    one.setBigDecimalValue(d_one.subtract(d_two));
                } else if (lex.getValue().equals("*")) {
//                    one.setDouleValue(d_one * d_two);
                    one.setBigDecimalValue(d_one.multiply(d_two));
                } else if (lex.getValue().equals("/")) {
//                    one.setDouleValue(d_one / d_two);
                    one.setBigDecimalValue(d_one.divide(d_two, 32, RoundingMode.HALF_UP));
                }
                _polskLexemes.remove(i);
                _polskLexemes.remove(i-1);
                i = 0;
            } else if (lex.getType() == Lexeme.TYPE_ZNAK && lex.getNumberCountUsed() == 1) {
                Lexeme one = (Lexeme) _polskLexemes.get(i - 1);
//                double d_one = one.getDouleValue();
                BigDecimal d_one = one.getBigDecimalValue();
                if (lex.getValue().equals("-")) {
//                    one.setDouleValue(-d_one);
                    one.setBigDecimalValue(d_one.negate());
                }
                _polskLexemes.remove(i);
                i = 0;
            } else if (lex.getType() == Lexeme.TYPE_FUNCTION) {

                Lexeme one = (Lexeme) _polskLexemes.get(i - 1);
                lexemesInFunc.add(one);

                Function func = (Function) functions.get(lex.getValue());

                one.setBigDecimalValue(func.count(lexemesInFunc));
                _polskLexemes.remove(i);

                lexemesInFunc.clear();
                i = 0;
            } else {
                i++;
            }
        }
        return ((Lexeme)_polskLexemes.get(0)).getBigDecimalValue();


        // TODO: here we will count

        //return 0;
    }

    public void generateInvertedPolNotayion() throws CounterException {
        if (expression == null) {
            throw new CounterException();
        }
        PolNotationGenerator gen = new PolNotationGenerator(expression, grammar);

        polskLexemes = gen.getInvertedPolNotayionList(functions);
//        _polskLexemes = new LinkedList();
//        lexemesInFunc = new LinkedList();
        _polskLexemes = new ArrayList();
        lexemesInFunc = new ArrayList();
    }

    public void addFunction(int type, String prototype, String body) throws CounterException {
        addFunction(new Function(type, prototype, body, functions, grammar));
    }

    private void addFunction(Function func) {
        functions.put(func.getName(), func);
    }

    public String addParameter(String name) throws CounterException {
        return addParameter(new Parameter(name));
    }

    private String addParameter(Parameter param) {
        params.put(param.getName(), param);
        return param.getName();
    }

    public void setParameter(String key, double value) throws CounterException {
        Parameter param = (Parameter) params.get(key);
        if (param == null) {
            throw new CounterException();
        }
        param.setValue(value);
    }
}
